#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <GL/glew.h>
#include <GL/glut.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <cutil.h>
#include <cutil_gl_error.h>
#include <cuda_gl_interop.h>

#include "boids.h"

GLuint ghoul[BOID_N_BODIES];
GLuint ghoulCount = 0;

float  coll[BOID_N_BODIES], *d_coll;
float4 elem[BOID_N_BODIES], *d_elem;

GLuint vbo;

float directions[] = {NORTH, EAST, SOUTH, WEST};

extern GLuint mapBits[MAPW][MAPH];
int *d_bitmap;

static GLuint isInitialized = 0;
static unsigned int nBodies = BOID_N_BODIES;

extern "C" void computeKernel(float4 *dptr, float4 *elem, 
			      int *d_bitmap, unsigned int nBodies);

static inline GLuint getRandomDir(void)
{
	return random() % 4;
}

static inline int getRandomGhoulID(void)
{
  	return random() % BOID_N_BODIES;
}

static inline GLfloat genNum(void)
{
	return ((random() % 1000) * 0.001);
}

int checkRegionOK(GLfloat x, GLfloat y)
{
	GLint viewport[4];
	GLdouble modelview[16];
	GLdouble projection[16];
	GLdouble winX, winY, winZ;
	GLdouble posX, posY, posZ;
	GLfloat c[4];

	glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);
	glGetIntegerv(GL_VIEWPORT, viewport);

	posX = (double) x;
	posY = (double) y;
	posZ = 1.0;

	gluProject(posX, posY, posZ, 
		   modelview, projection, viewport, 
		   &winX, &winY, &winZ);

	GLint wX = (GLint) winX;
	GLint wY = (GLint) winY;

	glReadPixels(wX, wY, 1, 1, GL_RGBA, GL_FLOAT, c);

	return mapBits[wX][wY];
}

void createVBO(GLuint *vbo)
{
	glGenBuffers(1, vbo);
	glBindBuffer(GL_ARRAY_BUFFER, *vbo);
	unsigned int size = BOID_N_BODIES * sizeof(float) * 4 * 3 * 2; //4
	glBufferData( GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
	glBindBuffer( GL_ARRAY_BUFFER, 0);
	CUDA_SAFE_CALL(cudaGLRegisterBufferObject(*vbo));
	CUT_CHECK_ERROR_GL();
}

void initCUDA(void)
{
	const unsigned int size = BOID_N_BODIES * sizeof(float) * 4;
	createVBO(&vbo);
	CUDA_SAFE_CALL(cudaMalloc((void **) &d_elem, size));
	CUDA_SAFE_CALL(cudaMemset(d_elem, 0, size));

	CUDA_SAFE_CALL(cudaMalloc((void **) &d_bitmap, MAPW * MAPH * sizeof(int)));
	CUDA_SAFE_CALL(cudaMemset(d_bitmap, 0, MAPW * MAPH * sizeof(int)));
}

void initOther(void)
{
	initCUDA();
}

void initBoids(void)
{
	int i, sign[] = {-1, 1};

	if (isInitialized) return;

	isInitialized = 1;
    
	for(i = 0; i < BOID_N_BODIES; i++) {
		GLfloat _x = genNum() * sign[random() % 2];
		GLfloat _y = genNum() * sign[random() % 2];

		while (!checkRegionOK(_x, _y)) {
			_x = genNum() * sign[random() % 2];
			_y = genNum() * sign[random() % 2];
		}

		elem[i].x = _x;
		elem[i].y = _y;

		elem[i].z = directions[random() % 4];
		elem[i].w = 1.0f;
	}

	int gID = getRandomGhoulID();

	elem[gID].x = 0.0;
	elem[gID].y = 0.0;
	elem[gID].w = 2.0f;

	for(int i = 0; i < N_COPS; i++) {
		int copIdx = random() % BOID_N_BODIES;
		elem[copIdx].w = 3.0;
	}

	CUDA_SAFE_CALL(cudaMemcpy((void *)d_elem, elem, BOID_N_BODIES * sizeof(float) * 4,
				  cudaMemcpyHostToDevice));
	CUDA_SAFE_CALL(cudaMemcpy((void *)d_bitmap, mapBits, 
				  MAPW * MAPH * sizeof(int),
				  cudaMemcpyHostToDevice));

}

void plotAll(void)
{
	glMatrixMode(GL_MODELVIEW);                                           
	glLoadIdentity();                                            

	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glVertexPointer(4, GL_FLOAT, 0, 0);
	glColorPointer(4, GL_FLOAT, 0, 
		       (GLvoid *) (BOID_N_BODIES * 3 * sizeof(float) * 4));

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
                                                                                
	glDrawArrays(GL_TRIANGLES, 0, nBodies * 3);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
}

void drawAllBoids(void)
{
	plotAll();
}

void makeBoidsUpdate(void)
{
	float4 *dptr;

	CUDA_SAFE_CALL(cudaGLMapBufferObject((void **) &dptr, vbo));
	computeKernel(dptr, d_elem, d_bitmap, nBodies);
	CUDA_SAFE_CALL(cudaGLUnmapBufferObject(vbo));
}
